package com.yricky.astral.signal

import com.yricky.astral.project.Hazuki
import com.yricky.astral.structure.INetList

interface Signal:Hazuki.Checkable {
    companion object{
        /**
         *
         */
        const val TAG_WIRE = "Wire"
        const val TAG_REG = "Reg"
        const val TAG_INNER = "Inner"
        const val TAG_INPUT = "Input"
        const val TAG_OUTPUT = "Output"
        const val TAG_CONST = "Const"
        const val TAG_DRIVEN = "Driven"
    }
    val width:Int
    val sValue: Number get() = 0
    val tags:MutableSet<String>

    fun generateBody():String
    fun level():Int = 0
}

fun Signal.isWire():Boolean{
    return tags.contains(Signal.TAG_WIRE)
}

fun Signal.isReg():Boolean{
    return tags.contains(Signal.TAG_REG)
}


/**
 * Signal declared directly in Verilog. Such as "wire" and "reg".
 */
interface AssignableSignal:Signal {
    val name:String
    val netList: INetList
    var sourceSignal:Signal?
    override val sValue: Number get()= sourceSignal?.sValue?:0
    fun generateLogic():String
    fun generateDeclare():String
    fun generateAssignTo():String

    override fun generateBody(): String {
        return name
    }

    /**
     * Define data source of the signal.
     *
     * Example:
     *
     * Astral code: outputWire("o") assignTo inputWire("i").inv()
     *
     * Generated verilog: assign o = (~i)
     *
     * Notice: For register signal, generated verilog code is also influenced by its Module.
     */
    infix fun assignTo(s:Signal):AssignableSignal{
        ensureSameWidth(s)
        sourceSignal = s
        if(s.tags.contains(Signal.TAG_CONST))
            standard.check(Hazuki.Rules.AssignToConstValue,"$name assign to ${generateAssignTo()}",Hazuki.CheckLevel.allow)
        return this
    }
}

infix fun Signal.ensureSameWidth(b:Signal){
    if(width != b.width)
        standard.check(Hazuki.Rules.WidthMismatch,"\"${generateBody()}\" and \"${b.generateBody()}\" says their width are not equal!")
}



